package com.rem.chat.websocket.netty;

import com.rem.chat.common.config.AppConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author: Themberfue
 * @date: 2025/6/12 19:04
 * @description:
 */
@Component
public class NettyWebSocketServer {
    private final Logger log = LoggerFactory.getLogger(NettyWebSocketServer.class);

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Channel serverChannel;

    @Resource
    AppConfig appConfig;

    @Resource
    HandlerWebSocket handlerWebSocket;

    @Bean(initMethod = "start", destroyMethod = "stop")
    public NettyWebSocketStarter NettyWebSocketStarter() {
        return new NettyWebSocketStarter();
    }

    public class NettyWebSocketStarter {
        public void start() throws InterruptedException {
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap()
                        .group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel nsc) throws Exception {
                                ChannelPipeline pipeline = nsc.pipeline();
                                pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
                                // Step 对 HTTP 协议的支持：HTTP 编码器，解码器
                                pipeline.addLast(new HttpServerCodec());
                                // Step 聚合解码 httpRequest/httpContent/lastHttpContent到fullHttpRequest：保证接收到的 HTTP 请求的完整性
                                pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                                // Step 心跳 long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit
                                // ? readerIdleTime 读超时时间，即服务端一定时间内未接收到客户端的消息
                                // ? writerIdleTime 写超时时间，即服务器一定时间内向客户端发送消息
                                // ? allIdleTime 所有事件超时时间
                                pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
                                pipeline.addLast(new HandlerHeartBeat());
                                // Step 将 HTTP协议升级为 WebSocket协议，对 WebSocket支持
                                pipeline.addLast(new WebSocketServerProtocolHandler(
                                        "/ws", null,
                                        true, 65536,
                                        true, true,
                                        10000L));
                                pipeline.addLast(handlerWebSocket);
                            }
                        });
                // ! 处理多实例启动时，端口冲突问题
                String wsPortStr = System.getProperty("ws.port");
                Integer wsPort = appConfig.getWebsocketPort();
                if (StringUtils.hasText(wsPortStr)) {
                    wsPort = Integer.parseInt(wsPortStr);
                }
                serverChannel = serverBootstrap.bind(wsPort).sync().channel();
                log.info("Netty WebSocket服务器启动在端口: {}", wsPort);
                serverChannel.closeFuture().addListener(future -> {
                    if (future.isSuccess()) {
                        log.info("Netty WebSocket服务器已正常关闭");
                    } else {
                        log.error("Netty WebSocket服务器关闭失败", future.cause());
                    }
                });
            } catch (InterruptedException e) {
                log.error("Netty WebSocket服务器启动失败", e);
                stop();
                throw e;
            }
        }

        public void stop() {
            if (serverChannel != null) {
                serverChannel.close().awaitUninterruptibly();
            }
            if (workerGroup != null) {
                workerGroup.shutdownGracefully().awaitUninterruptibly();
            }
            if (bossGroup != null) {
                bossGroup.shutdownGracefully().awaitUninterruptibly();
            }
            log.info("Netty WebSocket服务器已停止");
        }
    }
}
